//之前学了进程的控制和等待的知识,现在可以试着自己实现一个简陋版的shell
//这个shell需要循环以下的过程:
//1.获取命令行
//2.解析命令行
//3.建立一个子进程
//4.替换子进程
//5.父进程等待子进程退出

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <string.h>

#define NUM 128
#define CMD_NUM 64

using namespace std;

//c++实现
//不要作死了,调用c语言的函数接口,用c++的容器去实现其中的一些细节
//导致最后接口参数类型不符合,不能调用
//以后c语言的函数接口,老老实实的用c语言去实现吧
//int main()
//{
//    string command;
//    vector<string> argv;
//
//    cout << getpid() << endl;
//
//    for (;;)
//    {
//        //1.打印提示符
//        //清理上一次接收到的命令
//        command[0] = '\0';
//        cout << "[qingdiaoshen@VM-16-12-centos easy_shell]#";
//        fflush(stdout);
//
//        //2.获取命令字符串
//        getline(cin, command);
//
//        //3.解析命令字符串
//        size_t pos1 = 0;
//        size_t pos2;
//        size_t i = 0;
//        //需要提前设置好空间
//        argv.resize(64);
//        //以每一个" ",作为间隙,提取到上一个" "之间的指令
//        while((pos2 = command.find(" ", pos1)) != string::npos)
//        {
//           argv[i] = command.substr(pos1, pos2 - pos1);
//           ++i;
//           pos1 = pos2 + 1;
//        }
//
//        if ((pos2 = command.find(" ", pos1)) == string::npos)
//        {
//            argv[i] = command.substr(pos1);
//        }
//
//        //4.检测命令(内建命令)是否需要shell本身运行,而不是创建子进程完成
//        //Linux系统为了便于运维人员对系统的操作
//        //所以内建了很多shell命令
//        //一般来说linux系统的内建命令会比执行外部的shell命令执行更快
//        //因为执行内建命令相当于调用当前shell进程里面的函数
//        //而执行外部命令的话需要出发IO操作还要fork一个单独的进程来执行，执行完成后再退出。
//        if (argv[0] == "cd")
//        {
//             const char* ret = argv[1].c_str();
//            if (ret != nullptr)
//            {
//                chdir(argv[1].c_str());
//                continue;
//            }
//        }
//        
//        //5.执行第三方命令
//        int j = 0;
//        char argv1[64][64] = {'\0'};
//        //这里可以直接进行判断,因为string有运算符重载
//        //如果转化为const char*,反而需要strcmp进行判断
//        while(argv[j] != "")
//        {
//           strcpy(argv1[j], argv[j].c_str());
//           j++;
//        }
//        char* argv2[64 + 1] = {nullptr};
//        int k = 0;
//        //注意这里不能要多余的'\0'进入argv2,否则会出错
//        //需要控制,只能要指令本身
//        while(argv1[k][0] != '\0')
//        {
//            argv2[k] = argv1[k];
//            k++;
//        }
//        //上面这些操作解释如下,argv是一个vector<string>
//        //要提取argv里面分离好的命令,需要把string.c_str()依次赋值到
//        //指针数组里面来,本来以为strcpy可以满足我的需求,但是
//        //指针数组不能使用strcpy,编译时不会出错,但是运行时会出错
//        //因为我定义的char* 数组,里面的指针,指向的地址是随机的
//        //可以指向内存的任何地方,所以是不具有写的权限的
//        //所以这里只能使用二维数组
//        //然后将二维数组的每一行的地址再赋值给char* 数组
//        //因为execvp是需要char* 数组作为参数的
//
//        if (fork() == 0)
//        {
//            cout << getpid() << endl;
//            execvp(argv[0].c_str(), argv2);
//            exit(1);
//        }
//
//        int status = 0;
//        waitpid(-1, &status, 0);
//        cout << "child status: " << WIFEXITED(status)<< endl;
//
//        //这个也记得清除,不然会有指令残留的问题
//        argv.clear();
//    }
//
//
//    return 0;
//}


//c语言实现
int main()
{
    char command[NUM];

    for(;;)
    {
        char* argv[CMD_NUM] = {nullptr};

        //1.打印提示符
        //这种方式,可以做到O(1)的时间复杂度,清空字符串
        command[0] = 0;
        cout << "[qingdiaoshen@VM-16-12-centos easy_shell]#";
        fflush(stdout);

        //2.获取命令字符串
        fgets(command, NUM, stdin);

        //fgets()会获取到回车键,需要进行处理
        command[strlen(command) - 1] = '\0';

        //3.解析命令字符串
        const char* stop = " ";
        //command是需要被分割的字符串,stop是分割的标志
        argv[0] = strtok(command, stop);
        int i = 1;
        //可以指定一个空指针,这样函数继续扫描上一次函数成功调用结束的字符串位置
        //扫描到字符串末尾,始终返回空指针
        while(argv[i] = strtok(nullptr, stop))
        {
            ++i;
        }

        //4.检测命令是否是shell本身执行的,内建命令
        //运行内建命令,不会创建子进程,让父进程shell自己执行
        //相等于调用了自己的一个函数
        if (strcmp(argv[0], "cd") == 0)
        {
            if (argv[1] != nullptr)
            {
                chdir(argv[1]);
                continue;
            }
        }

        //5.执行第三方命令
        if (fork() == 0)
        {
            execvp(argv[0], argv);
            exit(1);
        }

        int status = 0;
        waitpid(-1, &status, 0);

        cout << "exit status: " << ((status >> 8) & 0xFF) << endl;

    }
}

